@load eucaClusterHandler

module EUCA_S;

export {
redef enum Notice += {
	EucaResFail,		# |failed descrete reservations access| > threshold
	EucaResSuccess,		# |succedded descrete reservations access| > threshold
	EucaConnFail,	# net inter-reserv domain connections fail
	EucaConnSuccess,	# net inter-reserv domain connections succeeded
	EucaUnregisteredAddress, # seen address that is not registered, but in known euca subnets

	EucaLocalScan,		# source of scan/portmap internal and registered
	EucaUnregScan,		# source of scan/portmap internal, but not registered
};
	

global pub_nets: set[subnet];
global priv_nets: set[subnet];

type scanObj: record {
	success: set[string];
	fail: set[string];
	success_count: count &default = 0;
	fail_count: count &default = 0;
};

# track successful connections between reservation peers
global res_peers: table[string] of scanObj;

# set of white listed ports whose servies are ignored
global allowed_ports: set[port] = { 80/tcp, } &redef;

# addresses to skip 
global skip_source_ip: set[addr] &redef;
global skip_dest_ip: set[addr] = { 255.255.255.255, } &redef;

# unregistered addresses that have been alarmed on - we do not want a 
#  notice for each and every connection
global unreg_addr_alarm: set[addr];

# thresholds!
# these list total number of connections allowed before notification
global conn_success_thresh: count = 25 &redef;
global conn_fail_thresh: count = 25 &redef;
# number of unique reservation spaces that can be traversed
#  before a notice is sent - both success and fail
global reserv_success_thresh: count = 1 &redef;
global reserv_fail_thresh: count = 1 &redef;

# info re wrappers on notices
global interestingNotices = { 
#	"TRWAddressScan", 
	"AddressScan", 
	"PortScan",
#	"SensitivePortmapperAccess", 
} &redef;

}

#
# ##################################################################
#

# Look at c$id$orig_h and if the conn starts from inside
#  the cluster, set (prov/public).  This is a different test
#  from ip_reservation since it will identify the use of addresses
#  used without registration.
#
function euca_a_init(a: addr) : count
{
	local ret_val: count = 0;

	if ( a in pub_nets ) 
		ret_val = 1;

	if ( a in priv_nets ) 
		ret_val = 2;

	return ret_val;
}

function ip_reservation(a: addr): string
{
	# take address 'a', return "EXTERNAL", "INTERNAL"
	#  or reservation id string
	#
	local orig_res: string = "EXTERNAL";
	local arv = euca_a_init(a);

	if ( arv > 0 )
		orig_res = "INTERNAL";

	if ( a in EUCA::ip_map )
		orig_res = EUCA::ip_map[a];

	return orig_res;
}

function check_reservation(orig_res: string, resp_res: string, established: bool) : scanObj
{
	local t_scanObj: scanObj;

	if ( orig_res !in res_peers ) {

		# new record	
		local t_success: set[string];
		local t_fail: set[string];

		if ( established ) {
			add t_success[resp_res];
			++t_scanObj$success_count;
			}
		else {
			add t_fail[resp_res];
			++t_scanObj$fail_count;
			}
		}
	else {
		# pull up old record
		t_scanObj = res_peers[orig_res];

		if ( established ) {
			
			++t_scanObj$success_count;

			if ( resp_res !in t_scanObj$success ) 
				add t_scanObj$success[resp_res];

			}
		else {
			++t_scanObj$fail_count;

			if ( resp_res !in t_scanObj$fail )
				add t_scanObj$fail[resp_res];
			}
		}

	res_peers[orig_res] = t_scanObj;
	return t_scanObj;
}


function check_scan(c: connection, established: bool) : bool
{
	local t_scanObj: scanObj;

	local orig_h: addr = c$id$orig_h; 
	local resp_h: addr = c$id$resp_h; 

	local orig_res = ip_reservation(orig_h);
	local resp_res = ip_reservation(resp_h);

	# If the source IP is external, we bail
	if ( orig_res == "EXTERNAL" ) 
		return F;

	if ( ( orig_h in skip_source_ip ) || ( resp_h in skip_dest_ip ) )
		return F;

	# Test this before the equivilancy test below to catch 
	#  one unregistered IP talking to another
	if ( orig_res == "INTERNAL" ) {

		if (orig_h !in unreg_addr_alarm) {

			NOTICE([$note=EucaUnregisteredAddress,
				$msg=fmt(" %s/%s -> %s/%s",
				orig_h, orig_res, resp_h, resp_res)]);

			add unreg_addr_alarm[orig_h];
			}

		return F;
		}

	# As well communication between instance within the
	#  same reservation.
	if ( orig_res == resp_res )
		return F;

	# Naive scan detection is somewhat problematic since we can 
	#  have legitimate activity between the various VMs.  For now
	#  just track unsuccessful connections and successful
	#  connections to non-whitlisted ports.
	# More detailed policy would be useful, but we must start somewhere.	
	
	if ( c$id$resp_p in allowed_ports ) 
		return F;

	t_scanObj = check_reservation(orig_res, resp_res, established);
	
	if ( t_scanObj$success_count == conn_success_thresh ) {

		NOTICE([$note=EucaConnSuccess,
			$msg=fmt(" Inter-Reserv connections: %s/%s -> %s/%s %s successful conn ",
			orig_h, orig_res, resp_h, resp_res, conn_success_thresh)]);
		}

	if ( t_scanObj$fail_count == conn_fail_thresh ) {

		NOTICE([$note=EucaConnFail,
			$msg=fmt(" Inter-Reserv connections: %s/%s -> %s/%s %s failed conn ",
			orig_h, orig_res, resp_h, resp_res, conn_fail_thresh)]);
		}

	if ( | t_scanObj$success | == reserv_success_thresh ) {

		NOTICE([$note=EucaResSuccess,
			$msg=fmt(" Inter-Reserv traffic: %s/%s -> %s/%s %s successful conn ",
			orig_h, orig_res, resp_h, resp_res, reserv_success_thresh)]);
		}

	if ( | t_scanObj$fail | == reserv_fail_thresh ) {

		NOTICE([$note=EucaResFail,
			$msg=fmt(" Inter-Reserv connection: %s/%s -> %s/%s %s successful conn ",
			orig_h, orig_res, resp_h, resp_res, reserv_fail_thresh)]);
		}

	return T;

}



event connection_attempt(c: connection)
	{
	Scan::check_scan(c, F, c$orig$state == TCP_INACTIVE);

	local trans = get_port_transport_proto(c$id$orig_p);
	if ( trans == tcp && TRW::use_TRW_algorithm )
		TRW::check_TRW_scan(c, conn_state(c, trans), F);
	}

# For the list of notices in the interestingNotices filter we can get the following
#  information from the notice itself: $src=orig, $p=service, $n=n, $msg
#
# We will scoop up the interesting notices and re-alert them if they involve an entity
#  in the euca address space.
#
event notice_action(n: notice_info, action: NoticeAction)
{
	# format the notice name 
	local noteName = fmt("%s", n$note);

	if    ( noteName in interestingNotices )
		{
		local source: addr;
		local p: port;
		local nn: count;
		local msg: string;

		# While we know that the values are supposed to be set, can't hurt
		#  to be a little cautious
		if ( n?$src )
			source = n$src;
		else
			source=127.0.0.1;

		if ( n?$p )
			p = n$p;
		else
			p = 0/tcp;

		if ( n?$n )
			nn = n$n;
		else
			nn=0;

		if ( n?$msg )
			msg = n$msg;
		else
			msg = "UNKNOWN";

		# Take these "clean" values and check to see if they should
		# be alarmed on
		local source_res = ip_reservation(source);

		# If the source IP is external, we bail
		if ( source_res == "EXTERNAL" ) 
			exit;

		if ( source_res == "INTERNAL" ) {

			NOTICE([$note=EucaUnregScan,
				$msg=fmt("%s [%s] %s", source, noteName, msg)]);
			}
		else {
		
			NOTICE([$note=EucaLocalScan,
				$msg=fmt("%s/%s [%s] %s", source, source_res, noteName, msg)]);
			}

	} # end noteName test
} #end event
